/**
 * Copyright (C) 2002-2015   The FreeCol Team
 * <p>
 * This file is part of FreeCol.
 * <p>
 * FreeCol is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * <p>
 * FreeCol is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.freecol.common.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.stream.XMLStreamException;

import net.sf.freecol.common.io.FreeColXMLReader;
import net.sf.freecol.common.io.FreeColXMLWriter;
import net.sf.freecol.common.model.Occupation;
import net.sf.freecol.common.model.Stance;

import static net.sf.freecol.common.util.CollectionUtils.*;

import net.sf.freecol.common.util.LogBuilder;
import net.sf.freecol.common.util.RandomChoice;


/**
 * Represents a colony. A colony contains {@link Building}s and
 * {@link ColonyTile}s. The latter represents the tiles around the
 * <code>Colony</code> where working is possible.
 */
public class Colony extends Settlement implements Nameable, TradeLocation
{

    private static final Logger logger = Logger.getLogger( Colony.class.getName( ) );

    public static final String REARRANGE_WORKERS = "rearrangeWorkers";
    public static final int LIBERTY_PER_REBEL = 200;
    /** The number of turns of advanced warning of starvation. */
    public static final int FAMINE_TURNS = 3;

    public static enum ColonyChangeEvent
    {
        POPULATION_CHANGE,
        PRODUCTION_CHANGE,
        BONUS_CHANGE,
        WAREHOUSE_CHANGE,
        BUILD_QUEUE_CHANGE,
        UNIT_TYPE_CHANGE
    }

    /** Reasons for not building a buildable. */
    public static enum NoBuildReason
    {
        NONE,
        NOT_BUILDING,
        NOT_BUILDABLE,
        POPULATION_TOO_SMALL,
        MISSING_BUILD_ABILITY,
        MISSING_ABILITY,
        WRONG_UPGRADE,
        COASTAL,
        LIMIT_EXCEEDED
    }

    /** A map of Buildings, indexed by the id of their basic type. */
    protected final java.util.Map< String, Building > buildingMap = new HashMap<>( );

    /** A list of the ColonyTiles. */
    protected final List< ColonyTile > colonyTiles = new ArrayList<>( );

    /** A map of ExportData, indexed by the ids of GoodsTypes. */
    protected final java.util.Map< String, ExportData > exportData = new HashMap<>( );

    /**
     * The number of liberty points.  Liberty points are an
     * abstract game concept.  They are generated by but are not
     * identical to bells, and subject to further modification.
     */
    protected int liberty;

    /** The SoL membership this turn. */
    protected int sonsOfLiberty;

    /** The SoL membership last turn. */
    protected int oldSonsOfLiberty;

    /** The number of tories this turn. */
    protected int tories;

    /** The number of tories last turn. */
    protected int oldTories;

    /** The current production bonus. */
    protected int productionBonus;

    /**
     * The number of immigration points.  Immigration points are an
     * abstract game concept.  They are generated by but are not
     * identical to crosses.
     */
    protected int immigration;

    /** The turn in which this colony was established. */
    protected Turn established = new Turn( 0 );

    /** A list of items to be built. */
    protected final BuildQueue< BuildableType > buildQueue
            = new BuildQueue<>( this,
                                BuildQueue.CompletionAction.REMOVE_EXCEPT_LAST,
                                Consumer.COLONY_PRIORITY );

    /** The colonists that may be born. */
    protected final BuildQueue< UnitType > populationQueue
            = new BuildQueue<>( this,
                                BuildQueue.CompletionAction.SHUFFLE,
                                Consumer.POPULATION_PRIORITY );

    // Will only be used on enemy colonies:
    protected int displayUnitCount = - 1;

    // Do not serialize below.

    /** Contains information about production and consumption. */
    private final ProductionCache productionCache = new ProductionCache( this );

    /** The occupation tracing status.  Do not serialize. */
    private boolean traceOccupation = false;


    /**
     * Constructor for ServerColony.
     *
     * @param game The enclosing <code>Game</code>.
     * @param owner The <code>Player</code> owning this <code>Colony</code>.
     * @param name The name of the new <code>Colony</code>.
     * @param tile The containing <code>Tile</code>.
     */
    protected Colony( Game game, Player owner, String name, Tile tile )
    {
        super( game, owner, name, tile );
    }

    /**
     * Create a new <code>Colony</code> with the given
     * identifier. The object should later be initialized by calling
     * either {@link #readFromXML(FreeColXMLReader)}.
     *
     * @param game The enclosing <code>Game</code>.
     * @param id The object identifier.
     */
    public Colony( Game game, String id )
    {
        super( game, id );
    }


    // Primitive accessors.

    /**
     * Gets a <code>List</code> of every {@link Building} in this
     * <code>Colony</code>.
     *
     * @return A list of <code>Building</code>s.
     * @see Building
     */
    public List< Building > getBuildings( )
    {
        return new ArrayList<>( buildingMap.values( ) );
    }

    /**
     * Get building of the specified general type (note: *not*
     * necessarily the exact building type supplied, but the building
     * present in the colony that is a descendant of the ultimate
     * ancestor of the specified type).
     *
     * @param type The type of the building to get.
     * @return The <code>Building</code> found.
     */
    public Building getBuilding( BuildingType type )
    {
        return buildingMap.get( type.getFirstLevel( ).getId( ) );
    }

    /**
     * Gets a <code>List</code> of every {@link ColonyTile} in this
     * <code>Colony</code>.
     *
     * @return A list of <code>ColonyTile</code>s.
     * @see ColonyTile
     */
    public List< ColonyTile > getColonyTiles( )
    {
        return colonyTiles;
    }

    /**
     * Get the <code>ColonyTile</code> matching the given
     * <code>Tile</code>.
     *
     * @param t The <code>Tile</code> to check.
     * @return The corresponding <code>ColonyTile</code>, or null if not found.
     */
    public ColonyTile getColonyTile( Tile t )
    {
        return find( colonyTiles, ct -> ct.getWorkTile( ) == t );
    }

    /**
     * Get the export date for a goods type.
     *
     * @param goodsType The <code>GoodsType</code> to check.
     * @return The required <code>ExportData</code>.
     */
    public ExportData getExportData( final GoodsType goodsType )
    {
        ExportData result = exportData.get( goodsType.getId( ) );
        if ( result == null )
        {
            result = new ExportData( goodsType );
            setExportData( result );
        }
        return result;
    }

    /**
     * Set some export data.
     *
     * @param newExportData A new <code>ExportData</code> value.
     */
    public final void setExportData( final ExportData newExportData )
    {
        exportData.put( newExportData.getId( ), newExportData );
    }

    /**
     * Gets the liberty points.
     *
     * @return The current liberty.
     */
    public int getLiberty( )
    {
        return liberty;
    }

    /**
     * Gets the production bonus of the colony.
     *
     * @return The current production bonus of the colony.
     */
    public int getProductionBonus( )
    {
        return productionBonus;
    }

    /**
     * Gets the immigration points.
     *
     * @return The current immigration.
     */
    public int getImmigration( )
    {
        return immigration;
    }

    /**
     * Modify the immigration points by amount given.
     *
     * @param amount An amount of immigration.
     */
    public void modifyImmigration( int amount )
    {
        immigration += amount;
    }

    /**
     * Get the turn this colony was established.
     *
     * @return The establishment <code>Turn</code>.
     */
    public Turn getEstablished( )
    {
        return established;
    }

    /**
     * Set the turn of establishment.
     *
     * @param newEstablished The new <code>Turn</code> of establishment.
     */
    public void setEstablished( final Turn newEstablished )
    {
        this.established = newEstablished;
    }

    /**
     * Get the <code>BuildQueue</code> contents.
     *
     * @return A list of <code>Buildable</code>s.
     */
    public List< BuildableType > getBuildQueue( )
    {
        return buildQueue.getValues( );
    }

    /**
     * Set the build queue value.
     *
     * @param newBuildQueue A list of new values for the build queue.
     */
    public void setBuildQueue( final List< BuildableType > newBuildQueue )
    {
        buildQueue.setValues( newBuildQueue );
    }


    // Occupation routines

    /**
     * Gets the occupation tracing status.
     *
     * @return The occupation tracing status.
     */
    public boolean getOccupationTrace( )
    {
        return this.traceOccupation;
    }

    /**
     * Sets the occupation tracing status.
     *
     * @param trace The new occupation tracing status.
     * @return The original occupation tracing status.
     */
    public boolean setOccupationTrace( boolean trace )
    {
        boolean ret = this.traceOccupation;
        this.traceOccupation = trace;
        return ret;
    }

    private void accumulateChoices( Collection< GoodsType > workTypes,
                                    Collection< GoodsType > tried,
                                    List< Collection< GoodsType > > result )
    {
        workTypes.removeAll( tried );
        if ( ! workTypes.isEmpty( ) )
        {
            result.add( workTypes );
            tried.addAll( workTypes );
        }
    }

    private void accumulateChoice( GoodsType workType,
                                   Collection< GoodsType > tried,
                                   List< Collection< GoodsType > > result )
    {
        if ( workType == null ) return;
        accumulateChoices( workType.getEquivalentTypes( ), tried, result );
    }

    /**
     * Get a list of collections of goods types, in order of priority
     * to try to produce in this colony by a given unit.
     *
     * @param unit The <code>Unit</code> to check.
     * @param userMode If a user requested this, favour the current
     *     work type, if not favour goods that the unit requires.
     * @return The list of collections of <code>GoodsType</code>s.
     */
    public List< Collection< GoodsType > > getWorkTypeChoices( Unit unit,
                                                               boolean userMode )
    {
        final Specification spec = getSpecification( );
        List< Collection< GoodsType > > result = new ArrayList<>( );
        Set< GoodsType > tried = new HashSet<>( );

        // Find the food and non-food goods types required by the unit.
        Set< GoodsType > food = new HashSet<>( );
        Set< GoodsType > nonFood = new HashSet<>( );
        for ( AbstractGoods ag : unit.getType( ).getConsumedGoods( ) )
        {
            if ( productionCache.getNetProductionOf( ag.getType( ) )
                    < ag.getAmount( ) )
            {
                if ( ag.getType( ).isFoodType( ) )
                {
                    food.addAll( ag.getType( ).getEquivalentTypes( ) );
                }
                else
                {
                    nonFood.addAll( ag.getType( ).getEquivalentTypes( ) );
                }
            }
        }

        if ( userMode )
        { // Favour current and expert types in user mode
            accumulateChoice( unit.getWorkType( ), tried, result );
            accumulateChoice( unit.getType( ).getExpertProduction( ), tried, result );
            accumulateChoice( unit.getExperienceType( ), tried, result );
            accumulateChoices( food, tried, result );
            accumulateChoices( nonFood, tried, result );
        }
        else
        { // Otherwise favour the required goods types
            accumulateChoices( food, tried, result );
            accumulateChoices( nonFood, tried, result );
            accumulateChoice( unit.getWorkType( ), tried, result );
            accumulateChoice( unit.getType( ).getExpertProduction( ), tried, result );
            accumulateChoice( unit.getExperienceType( ), tried, result );
        }
        accumulateChoices( spec.getFoodGoodsTypeList( ), tried, result );
        accumulateChoices( spec.getNewWorldLuxuryGoodsTypeList( ), tried, result );
        accumulateChoices( spec.getGoodsTypeList( ), tried, result );
        return result;
    }

    /**
     * Gets the best occupation for a given unit to produce one of
     * a given set of goods types.
     *
     * @param unit The <code>Unit</code> to find an
     *     <code>Occupation</code> for.
     * @param workTypes A collection of <code>GoodsType</code> to
     *     consider producing.
     * @param lb A <code>LogBuilder</code> to log to.
     * @return An <code>Occupation</code> for the given unit, or null
     *     if none found.
     */
    private Occupation getOccupationFor( Unit unit,
                                         Collection< GoodsType > workTypes,
                                         LogBuilder lb )
    {
        if ( workTypes.isEmpty( ) ) return null;

        Occupation best = new Occupation( null, null, null );
        int bestAmount = 0;
        for ( WorkLocation wl : getCurrentWorkLocations( ) )
        {
            bestAmount = best.improve( unit, wl, bestAmount, workTypes, lb );
        }

        if ( best.workLocation != null )
        {
            lb.add( "\n  => ", best, " = ", bestAmount );
        }
        return ( best.workLocation == null ) ? null : best;
    }

    /**
     * Gets the best occupation for a given unit.
     *
     * @param unit The <code>Unit</code> to find an
     *     <code>Occupation</code> for.
     * @param userMode If a user requested this, favour the current
     *     work type, if not favour goods that the unit requires.
     * @param lb A <code>LogBuilder</code> to log to.
     * @return An <code>Occupation</code> for the given unit, or
     *     null if none found.
     */
    private Occupation getOccupationFor( Unit unit, boolean userMode,
                                         LogBuilder lb )
    {
        for ( Collection< GoodsType > types : getWorkTypeChoices( unit, userMode ) )
        {
            lb.add( "\n  " );
            FreeColObject.logFreeColObjects( types, lb );
            Occupation occupation = getOccupationFor( unit, types, lb );
            if ( occupation != null ) return occupation;
        }
        lb.add( "\n  => FAILED" );
        return null;
    }

    /**
     * Gets the best occupation for a given unit to produce one of
     * a given set of goods types.
     *
     * @param unit The <code>Unit</code> to find an
     *     <code>Occupation</code> for.
     * @param workTypes A collection of <code>GoodsType</code> to
     *     consider producing.
     * @return An <code>Occupation</code> for the given unit, or null
     *     if none found.
     */
    private Occupation getOccupationFor( Unit unit,
                                         Collection< GoodsType > workTypes )
    {
        LogBuilder lb = new LogBuilder( ( getOccupationTrace( ) ) ? 64 : 0 );
        lb.add( getName( ), ".getOccupationFor(", unit, ", " );
        FreeColObject.logFreeColObjects( workTypes, lb );
        lb.add( ")" );

        Occupation occupation = getOccupationFor( unit, workTypes, lb );
        lb.log( logger, Level.WARNING );
        return occupation;
    }

    /**
     * Gets the best occupation for a given unit.
     *
     * @param unit The <code>Unit</code> to find an
     *     <code>Occupation</code> for.
     * @param userMode If a user requested this, favour the current
     *     work type, if not favour goods that the unit requires.
     * @return An <code>Occupation</code> for the given unit, or
     *     null if none found.
     */
    private Occupation getOccupationFor( Unit unit, boolean userMode )
    {
        LogBuilder lb = new LogBuilder( ( getOccupationTrace( ) ) ? 64 : 0 );
        lb.add( getName( ), ".getOccupationFor(", unit, ")" );

        Occupation occupation = getOccupationFor( unit, userMode, lb );
        lb.log( logger, Level.WARNING );
        return occupation;
    }


    // WorkLocations, Buildings, ColonyTiles

    /**
     * Get a stream of all the possible work locations for this colony.
     *
     * @return A suitable <code>Stream</code>.
     */
    private Stream< WorkLocation > getAllWorkLocationsStream( )
    {
        return Stream.concat( colonyTiles.stream( ),
                              buildingMap.values( ).stream( ) );
    }

    /**
     * Gets a list of every work location in this colony.
     *
     * @return The list of work locations.
     */
    public List< WorkLocation > getAllWorkLocations( )
    {
        return getAllWorkLocationsStream( )
                .collect( Collectors.toList( ) );
    }

    /**
     * Gets a list of all freely available work locations
     * in this colony.
     *
     * @return The list of available <code>WorkLocation</code>s.
     */
    public List< WorkLocation > getAvailableWorkLocations( )
    {
        return getAllWorkLocationsStream( )
                .filter( WorkLocation::isAvailable )
                .collect( Collectors.toList( ) );
    }

    /**
     * Gets a list of all current work locations in this colony.
     *
     * @return The list of current <code>WorkLocation</code>s.
     */
    public List< WorkLocation > getCurrentWorkLocations( )
    {
        return getAllWorkLocationsStream( )
                .filter( WorkLocation::isCurrent )
                .collect( Collectors.toList( ) );
    }

    /**
     * Add a Building to this Colony.
     *
     * Lower level routine, do not use directly in-game (use buildBuilding).
     * Used for serialization and public for the test suite.
     *
     * -til: Could change the tile appearance if the building is
     * stockade-type
     *
     * @param building The <code>Building</code> to build.
     * @return True if the building was added.
     */
    public boolean addBuilding( final Building building )
    {
        if ( building == null || building.getType( ) == null ) return false;
        final BuildingType buildingType = building.getType( ).getFirstLevel( );
        if ( buildingType == null || buildingType.getId( ) == null ) return false;
        buildingMap.put( buildingType.getId( ), building );
        addFeatures( building.getType( ) );
        return true;
    }

    /**
     * Remove a building from this Colony.
     *
     * -til: Could change the tile appearance if the building is
     * stockade-type
     *
     * @param building The <code>Building</code> to remove.
     * @return True if the building was removed.
     */
    protected boolean removeBuilding( final Building building )
    {
        final BuildingType buildingType = building.getType( ).getFirstLevel( );
        if ( buildingMap.remove( buildingType.getId( ) ) == null ) return false;
        removeFeatures( building.getType( ) );
        return true;
    }

    /**
     * Gets a building for producing a given type of goods.
     *
     * @param goodsType The <code>GoodsType</code> to produce.
     * @return A <code>Building</code> which produces
     *      the given type of goods, or null if not found.
     */
    public Building getBuildingForProducing( final GoodsType goodsType )
    {
        for ( Building b : buildingMap.values( ) )
        {
            if ( AbstractGoods.findByType( goodsType, b.getOutputs( ) ) != null )
            { return b; }
        }
        return null;
    }

    /**
     * Gets a work location with a given ability.
     *
     * @param ability An ability key.
     * @return A <code>WorkLocation</code> with the required
     *     <code>Ability</code>, or null if not found.
     */
    public WorkLocation getWorkLocationWithAbility( String ability )
    {
        for ( WorkLocation wl : getCurrentWorkLocations( ) )
        {
            if ( wl.hasAbility( ability ) ) return wl;
        }
        return null;
    }

    /**
     * Gets a work location of a specific class with a given ability.
     *
     * @param ability An ability key.
     * @param returnClass The expected subclass.
     * @return A <code>WorkLocation</code> with the required
     *     <code>Ability</code>, or null if not found.
     */
    public < T extends WorkLocation > T getWorkLocationWithAbility( String ability,
                                                                    Class< T > returnClass )
    {
        WorkLocation wl = getWorkLocationWithAbility( ability );
        if ( wl != null ) try { return returnClass.cast( wl ); } catch ( ClassCastException cce ) {} ;
        return null;
    }

    /**
     * Gets a work location with a given modifier.
     *
     * @param modifier A modifier key.
     * @return A <code>WorkLocation</code> with the required
     *     <code>Modifier</code>, or null if not found.
     */
    public WorkLocation getWorkLocationWithModifier( String modifier )
    {
        for ( WorkLocation wl : getCurrentWorkLocations( ) )
        {
            if ( wl.hasModifier( modifier ) ) return wl;
        }
        return null;
    }

    /**
     * Gets a work location of a specific class with a given modifier.
     *
     * @param modifier A modifier key.
     * @param returnClass The expected subclass.
     * @return A <code>WorkLocation</code> with the required
     *     <code>Modifier</code>, or null if not found.
     */
    public < T extends WorkLocation > T getWorkLocationWithModifier( String modifier,
                                                                     Class< T > returnClass )
    {
        WorkLocation wl = getWorkLocationWithModifier( modifier );
        if ( wl != null ) try { return returnClass.cast( wl ); } catch ( ClassCastException cce ) {}
        return null;
    }

    /**
     * Collect the work locations for consuming a given type of goods.
     *
     * @param goodsType The <code>GoodsType</code> to consume.
     * @return A list of <code>WorkLocation</code>s which consume
     *     the given type of goods.
     */
    public List< WorkLocation > getWorkLocationsForConsuming( GoodsType goodsType )
    {
        return getCurrentWorkLocations( ).stream( )
                .filter( wl -> any( wl.getInputs( ), ag -> ag.getType( ) == goodsType ) )
                .collect( Collectors.toList( ) );
    }

    /**
     * Collect the work locations for producing a given type of goods.
     *
     * @param goodsType The <code>GoodsType</code> to produce.
     * @return A list of <code>WorkLocation</code>s which produce
     *     the given type of goods.
     */
    public List< WorkLocation > getWorkLocationsForProducing( GoodsType goodsType )
    {
        return getCurrentWorkLocations( ).stream( )
                .filter( wl -> any( wl.getOutputs( ), ag -> ag.getType( ) == goodsType ) )
                .collect( Collectors.toList( ) );
    }

    /**
     * Find a work location for producing a given type of goods.
     * Beware that this may not be the optimal location for the
     * production, for which {@link #getWorkLocationFor} is better.
     *
     * @param goodsType The <code>GoodsType</code> to produce.
     * @return A <code>WorkLocation</code>s which produces
     *      the given type of goods, or null if not found.
     */
    public WorkLocation getWorkLocationForProducing( GoodsType goodsType )
    {
        List< WorkLocation > wls = getWorkLocationsForProducing( goodsType );
        return ( wls.isEmpty( ) ) ? null : wls.get( 0 );
    }

    /**
     * Gets the work location best suited for the given unit to
     * produce a type of goods.
     *
     * @param unit The <code>Unit</code> to get the building for.
     * @param goodsType The <code>GoodsType</code> to produce.
     * @return The best <code>WorkLocation</code> found.
     */
    public WorkLocation getWorkLocationFor( Unit unit, GoodsType goodsType )
    {
        if ( goodsType == null ) return getWorkLocationFor( unit );
        Occupation occupation
                = getOccupationFor( unit, goodsType.getEquivalentTypes( ) );
        return ( occupation == null ) ? null : occupation.workLocation;
    }

    /**
     * Gets the work location best suited for the given unit.
     *
     * @param unit The <code>Unit</code> to check for.
     * @return The best <code>WorkLocation</code> found.
     */
    public WorkLocation getWorkLocationFor( Unit unit )
    {
        Occupation occupation = getOccupationFor( unit, false );
        return ( occupation == null ) ? null : occupation.workLocation;
    }

    /**
     * Is a tile actually in use by this colony?
     *
     * @param tile The <code>Tile</code> to test.
     * @return True if this tile is actively in use by this colony.
     */
    public boolean isTileInUse( Tile tile )
    {
        ColonyTile colonyTile = getColonyTile( tile );
        return colonyTile != null && ! colonyTile.isEmpty( );
    }

    /**
     * Get the warehouse-type building in this colony.
     *
     * @return The warehouse <code>Building</code>.
     */
    public Building getWarehouse( )
    {
        return getWorkLocationWithModifier( Modifier.WAREHOUSE_STORAGE,
                                            Building.class );
    }

    /**
     * Does this colony have a stockade?
     *
     * @return True if the colony has a stockade.
     */
    public boolean hasStockade( )
    {
        return getStockade( ) != null;
    }

    /**
     * Gets the stockade building in this colony.
     *
     * @return The stockade <code>Building</code>.
     */
    public Building getStockade( )
    {
        return getWorkLocationWithModifier( Modifier.DEFENCE, Building.class );
    }

    /**
     * Gets the stockade key, as should be visible to the owner
     * or a player that can see this colony.
     *
     * @return The stockade key, or null if no stockade-building is present.
     */
    public String getStockadeKey( )
    {
        Building stockade = getStockade( );
        return ( stockade == null ) ? null : stockade.getType( ).getSuffix( );
    }

    /**
     * Get a weighted list of natural disasters than can strike this
     * colony.  This list comprises all natural disasters that can
     * strike the colony's tiles.
     *
     * @return A weighted list of <code>Disaster</code>s.
     */
    public List< RandomChoice< Disaster > > getDisasters( )
    {
        List< RandomChoice< Disaster > > disasters = new ArrayList<>( );
        for ( ColonyTile tile : colonyTiles )
        {
            disasters.addAll( tile.getWorkTile( ).getDisasters( ) );
        }
        return disasters;
    }


    // What are we building?  What can we build?

    /**
     * Is a building type able to be automatically built at no cost.
     * True when the player has a modifier that collapses the cost to zero.
     *
     * @param buildingType a <code>BuildingType</code> value
     * @return True if the building is available at zero cost.
     */
    public boolean isAutomaticBuild( BuildingType buildingType )
    {
        float value = owner.applyModifiers( 100f, getGame( ).getTurn( ),
                                            Modifier.BUILDING_PRICE_BONUS, buildingType );
        return value == 0f && canBuild( buildingType );
    }

    /**
     * Gets a list of every unit type this colony may build.
     *
     * @return A list of buildable <code>UnitType</code>s.
     */
    public List< UnitType > getBuildableUnits( )
    {
        return getSpecification( ).getUnitTypeList( ).stream( )
                .filter( ut -> ut.needsGoodsToBuild( ) && canBuild( ut ) )
                .collect( Collectors.toList( ) );
    }

    /**
     * Returns how many turns it would take to build the given
     * <code>BuildableType</code>.
     *
     * @param buildable The <code>BuildableType</code> to build.
     * @return The number of turns to build the buildable, negative if
     *     some goods are not being built, UNDEFINED if none is.
     */
    public int getTurnsToComplete( BuildableType buildable )
    {
        return getTurnsToComplete( buildable, null );
    }

    /**
     * Returns how many turns it would take to build the given
     * <code>BuildableType</code>.
     *
     * @param buildable The <code>BuildableType</code> to build.
     * @param needed The <code>AbstractGoods</code> needed to continue
     *     the build.
     * @return The number of turns to build the buildable (which may
     *     be zero, UNDEFINED if no useful work is being done, negative
     *     if some requirement is or will block completion (value is
     *     the negation of (turns-to-blockage + 1), and if the needed
     *     argument is supplied it is set to the goods deficit).
     */
    public int getTurnsToComplete( BuildableType buildable,
                                   AbstractGoods needed )
    {
        final List< AbstractGoods > required = buildable.getRequiredGoods( );
        int turns = 0, satisfied = 0, failing = 0, underway = 0;

        ProductionInfo info = productionCache.getProductionInfo( buildQueue );
        for ( AbstractGoods ag : required )
        {
            final GoodsType type = ag.getType( );
            final int amountNeeded = ag.getAmount( );
            final int amountAvailable = getGoodsCount( type );
            if ( amountAvailable >= amountNeeded )
            {
                satisfied++;
                continue;
            }
            int production = productionCache.getNetProductionOf( type );
            if ( info != null )
            {
                AbstractGoods consumption = AbstractGoods.findByType( type,
                                                                      info.getConsumption( ) );
                if ( consumption != null )
                {
                    // add the amount the build queue itself will consume
                    production += consumption.getAmount( );
                }
            }
            if ( production <= 0 )
            {
                failing++;
                if ( needed != null )
                {
                    needed.setType( type );
                    needed.setAmount( amountNeeded - amountAvailable );
                }
                continue;
            }

            underway++;
            int amountRemaining = amountNeeded - amountAvailable;
            int eta = amountRemaining / production;
            if ( amountRemaining % production != 0 ) eta++;
            turns = Math.max( turns, eta );
        }

        return ( satisfied + underway == required.size( ) ) ? turns // Will finish
                : ( failing == required.size( ) ) ? UNDEFINED // Not even trying
                : - ( turns + 1 ); // Blocked by something
    }

    /**
     * Returns <code>true</code> if this Colony can breed the given
     * type of Goods. Only animals (such as horses) are expected to be
     * breedable.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return a <code>boolean</code> value
     */
    public boolean canBreed( GoodsType goodsType )
    {
        int breedingNumber = goodsType.getBreedingNumber( );
        return ( breedingNumber < GoodsType.INFINITY &&
                breedingNumber <= getGoodsCount( goodsType ) );
    }

    /**
     * Gets the type of building currently being built.
     *
     * @return The type of building currently being built.
     */
    public BuildableType getCurrentlyBuilding( )
    {
        return buildQueue.getCurrentlyBuilding( );
    }

    /**
     * Sets the current type of buildable to be built and if it is a building
     * insist that there is only one in the queue.
     *
     * @param buildable The <code>BuildableType</code> to build.
     */
    public void setCurrentlyBuilding( BuildableType buildable )
    {
        buildQueue.setCurrentlyBuilding( buildable );
    }

    public boolean canBuild( )
    {
        return canBuild( getCurrentlyBuilding( ) );
    }

    /**
     * Returns true if this Colony can build the given BuildableType.
     *
     * @param buildableType a <code>BuildableType</code> value
     * @return a <code>boolean</code> value
     */
    public boolean canBuild( BuildableType buildableType )
    {
        return getNoBuildReason( buildableType, null ) == NoBuildReason.NONE;
    }

    /**
     * Return the reason why the give <code>BuildableType</code> can
     * not be built.
     *
     * @param buildableType A <code>BuildableType</code> to build.
     * @param assumeBuilt An optional list of other buildable types
     *     which can be assumed to be built, for the benefit of build
     *     queue checks.
     * @return A <code>NoBuildReason</code> value decribing the failure,
     *     including <code>NoBuildReason.NONE</code> on success.
     */
    public NoBuildReason getNoBuildReason( BuildableType buildableType,
                                           List< BuildableType > assumeBuilt )
    {
        if ( buildableType == null )
        {
            return NoBuildReason.NOT_BUILDING;
        }
        else if ( ! buildableType.needsGoodsToBuild( ) )
        {
            return NoBuildReason.NOT_BUILDABLE;
        }
        else if ( buildableType.getRequiredPopulation( ) > getUnitCount( ) )
        {
            return NoBuildReason.POPULATION_TOO_SMALL;
        }
        else if ( buildableType.hasAbility( Ability.COASTAL_ONLY )
                && ! getTile( ).isCoastland( ) )
        {
            return NoBuildReason.COASTAL;
        }
        else
        {
            if ( ! all( buildableType.getRequiredAbilities( ).entrySet( ),
                        e -> e.getValue( ) == hasAbility( e.getKey( ) ) ) )
            {
                return NoBuildReason.MISSING_ABILITY;
            }
            if ( ! all( buildableType.getLimits( ), l -> l.evaluate( this ) ) )
            {
                return NoBuildReason.LIMIT_EXCEEDED;
            }
        }
        if ( assumeBuilt == null )
        {
            assumeBuilt = Collections.< BuildableType >emptyList( );
        }
        if ( buildableType instanceof BuildingType )
        {
            BuildingType newBuildingType = ( BuildingType ) buildableType;
            Building colonyBuilding = this.getBuilding( newBuildingType );
            if ( colonyBuilding == null )
            {
                // the colony has no similar building yet
                BuildingType from = newBuildingType.getUpgradesFrom( );
                if ( from != null && ! assumeBuilt.contains( from ) )
                {
                    // we are trying to build an advanced factory, we
                    // should build lower level shop first
                    return NoBuildReason.WRONG_UPGRADE;
                }
            }
            else
            {
                // a building of the same family already exists
                BuildingType from = colonyBuilding.getType( ).getUpgradesTo( );
                if ( from != newBuildingType && ! assumeBuilt.contains( from ) )
                {
                    // the existing building's next upgrade is not the
                    // new one we want to build
                    return NoBuildReason.WRONG_UPGRADE;
                }
            }
        }
        else if ( buildableType instanceof UnitType )
        {
            // Non-person units need a BUILD ability, present or assumed.
            if ( ! buildableType.hasAbility( Ability.PERSON )
                    && ! hasAbility( Ability.BUILD, buildableType )
                    && none( assumeBuilt, bt -> bt.hasAbility( Ability.BUILD,
                                                               buildableType ) ) )
            {
                return NoBuildReason.MISSING_BUILD_ABILITY;
            }
        }
        return NoBuildReason.NONE;
    }

    /**
     * Returns the price for the remaining hammers and tools for the
     * {@link Building} that is currently being built.
     *
     * @return The price.
     * @see net.sf.freecol.client.control.InGameController#payForBuilding
     */
    public int getPriceForBuilding( )
    {
        return getPriceForBuilding( getCurrentlyBuilding( ) );
    }

    /**
     * Gets the price for the remaining resources to build a given buildable.
     *
     * @param type The <code>BuildableType</code> to build.
     * @return The price.
     * @see net.sf.freecol.client.control.InGameController#payForBuilding
     */
    public int getPriceForBuilding( BuildableType type )
    {
        return priceGoodsForBuilding( getRequiredGoods( type ) );
    }

    /**
     * Gets a price for a map of resources to build a given buildable.
     *
     * @param required A list of required <code>AbstractGoods</code>.
     * @return The price.
     * @see net.sf.freecol.client.control.InGameController#payForBuilding
     */
    public int priceGoodsForBuilding( List< AbstractGoods > required )
    {
        final Market market = getOwner( ).getMarket( );
        // FIXME: magic number!
        return required.stream( ).mapToInt( ag -> ( ag.getType( ).isStorable( ) )
                ? ( market.getBidPrice( ag.getType( ), ag.getAmount( ) ) * 110 ) / 100
                : ag.getType( ).getPrice( ) * ag.getAmount( ) ).sum( );
    }

    /**
     * Gets a map of the types of goods and amount thereof required to
     * finish a buildable in this colony.
     *
     * @param type The <code>BuildableType</code> to build.
     * @return The map to completion.
     */
    public List< AbstractGoods > getRequiredGoods( BuildableType type )
    {
        List< AbstractGoods > result = new ArrayList<>( );
        for ( AbstractGoods goods : type.getRequiredGoods( ) )
        {
            GoodsType goodsType = goods.getType( );
            int remaining = goods.getAmount( ) - getGoodsCount( goodsType );
            if ( remaining > 0 )
            {
                result.add( new AbstractGoods( goodsType, remaining ) );
            }
        }
        return result;
    }

    /**
     * Gets all the goods required to complete a build.  The list
     * includes the prerequisite raw materials as well as the direct
     * requirements (i.e. hammers, tools).  If enough of a required
     * goods is present in the colony, then that type is not returned.
     * Take care to order types with raw materials first so that we
     * can prioritize gathering what is required before manufacturing.
     *
     * Public for the benefit of AI planning and the test suite.
     *
     * @param buildable The <code>BuildableType</code> to consider.
     * @return A list of required abstract goods.
     */
    public List< AbstractGoods > getFullRequiredGoods( BuildableType buildable )
    {
        if ( buildable == null ) return Collections.< AbstractGoods >emptyList( );

        List< AbstractGoods > required = new ArrayList<>( );
        for ( AbstractGoods ag : buildable.getRequiredGoods( ) )
        {
            int amount = ag.getAmount( );
            GoodsType type = ag.getType( );
            while ( type != null )
            {
                if ( amount <= this.getGoodsCount( type ) ) break; // Shortcut
                required.add( 0, new AbstractGoods( type,
                                                    amount - this.getGoodsCount( type ) ) );
                type = type.getInputType( );
            }
        }
        return required;
    }

    /**
     * Check if the owner can buy the remaining hammers and tools for
     * the {@link Building} that is currently being built.
     *
     * @exception IllegalStateException If the owner of this <code>Colony</code>
     *                has an insufficient amount of gold.
     * @see #getPriceForBuilding
     */
    public boolean canPayToFinishBuilding( )
    {
        return canPayToFinishBuilding( getCurrentlyBuilding( ) );
    }

    /**
     * Check if the owner can buy the remaining hammers and tools for
     * the {@link Building} given.
     *
     * @param buildableType a <code>BuildableType</code> value
     * @return a <code>boolean</code> value
     * @exception IllegalStateException If the owner of this <code>Colony</code>
     *                has an insufficient amount of gold.
     * @see #getPriceForBuilding
     */
    public boolean canPayToFinishBuilding( BuildableType buildableType )
    {
        return buildableType != null
                && getOwner( ).checkGold( getPriceForBuilding( buildableType ) );
    }


    // Liberty and the consequences

    /**
     * Adds to the liberty points by increasing the liberty goods present.
     * Used only by DebugMenu.
     *
     * @param amount The number of liberty to add.
     */
    public void addLiberty( int amount )
    {
        List< GoodsType > libertyTypeList = getSpecification( )
                .getLibertyGoodsTypeList( );
        final int uc = getUnitCount( );
        if ( calculateRebels( uc, sonsOfLiberty ) <= uc + 1
                && amount > 0
                && ! libertyTypeList.isEmpty( ) )
        {
            addGoods( libertyTypeList.get( 0 ), amount );
        }
        updateSoL( );
        updateProductionBonus( );
    }

    /**
     * Modify the liberty points by amount given.
     *
     * @param amount An amount of liberty.
     */
    public void modifyLiberty( int amount )
    {
        // Produced liberty always applies to the player (for FFs etc)
        getOwner( ).modifyLiberty( amount );

        liberty += amount;
        // Liberty can not meaningfully go negative.
        liberty = Math.max( 0, liberty );

        updateSoL( );
        updateProductionBonus( );

        // If the bell accumulation cap option is set, and the colony
        // has reached 100%, liberty can not rise higher.
        boolean capped = getSpecification( )
                .getBoolean( GameOptions.BELL_ACCUMULATION_CAPPED );
        if ( capped && sonsOfLiberty >= 100 )
        {
            liberty = LIBERTY_PER_REBEL * getUnitCount( );
        }
    }

    /**
     * Calculates the current SoL membership of the colony based on
     * the liberty value and colonists.
     */
    public void updateSoL( )
    {
        int uc = getUnitCount( );
        oldSonsOfLiberty = sonsOfLiberty;
        oldTories = tories;
        sonsOfLiberty = calculateSoLPercentage( uc, getLiberty( ) );
        tories = uc - calculateRebels( uc, sonsOfLiberty );
    }

    /**
     * Calculate the SoL membership percentage of the colony based on the
     * number of colonists and liberty.
     *
     * @param uc The proposed number of units in the colony.
     * @param liberty The amount of liberty.
     * @return The percentage of SoLs, negative if not calculable.
     */
    private int calculateSoLPercentage( int uc, int liberty )
    {
        if ( uc <= 0 ) return - 1;

        float membership = ( liberty * 100.0f ) / ( LIBERTY_PER_REBEL * uc );
        membership = applyModifiers( membership, getGame( ).getTurn( ),
                                     getOwner( ).getModifiers( Modifier.SOL ) );
        if ( membership < 0.0f )
        {
            membership = 0.0f;
        }
        else if ( membership > 100.0f )
        {
            membership = 100.0f;
        }
        return ( int ) membership;
    }

    /**
     * Calculate the SoL membership percentage of a colony.
     *
     * @return The percentage of SoLs, negative if not calculable.
     */
    public int getSoLPercentage( )
    {
        return calculateSoLPercentage( getUnitCount( ), getLiberty( ) );
    }

    /**
     * Calculate the number of rebels given a SoL percentage and unit count.
     *
     * @param uc The number of units in the colony.
     * @param solPercent The percentage of SoLs.
     */
    public static int calculateRebels( int uc, int solPercent )
    {
        return ( int ) Math.floor( 0.01 * solPercent * uc );
    }

    /**
     * Gets the Tory membership percentage of the colony.
     *
     * @return The current Tory membership of the colony.
     */
    public int getTory( )
    {
        return 100 - getSoL( );
    }

    /**
     * Update the colony's production bonus.
     *
     * @return True if the bonus changed.
     */
    protected boolean updateProductionBonus( )
    {
        final Specification spec = getSpecification( );
        final int veryBadGovernment
                = spec.getInteger( "model.option.veryBadGovernmentLimit" );
        final int badGovernment
                = spec.getInteger( "model.option.badGovernmentLimit" );
        final int veryGoodGovernment
                = spec.getInteger( "model.option.veryGoodGovernmentLimit" );
        final int goodGovernment
                = spec.getInteger( "model.option.goodGovernmentLimit" );
        int newBonus = ( sonsOfLiberty >= veryGoodGovernment ) ? 2
                : ( sonsOfLiberty >= goodGovernment ) ? 1
                : ( tories > veryBadGovernment ) ? - 2
                : ( tories > badGovernment ) ? - 1
                : 0;
        if ( productionBonus != newBonus )
        {
            invalidateCache( );
            productionBonus = newBonus;
            return true;
        }
        return false;
    }

    /**
     * Gets the number of units that would be good to add/remove from this
     * colony.  That is the number of extra units that can be added without
     * damaging the production bonus, or the number of units to remove to
     * improve it.
     *
     * @return The number of units to add to the colony, or if negative
     *      the negation of the number of units to remove.
     */
    public int getPreferredSizeChange( )
    {
        int i, limit, pop = getUnitCount( );
        if ( productionBonus < 0 )
        {
            limit = pop;
            for ( i = 1; i < limit; i++ )
            {
                if ( governmentChange( pop - i ) == 1 ) break;
            }
            return - i;
        }
        else
        {
            final Specification spec = getSpecification( );
            limit = spec.getInteger( "model.option.badGovernmentLimit" );
            for ( i = 1; i < limit; i++ )
            {
                if ( governmentChange( pop + i ) == - 1 ) break;
            }
            return i - 1;
        }
    }


    // Unit manipulation and population

    /**
     * Special routine to handle non-specific add of unit to colony.
     *
     * @param unit The <code>Unit</code> to add.
     * @return True if the add succeeds.
     */
    public boolean joinColony( Unit unit )
    {
        Occupation occupation = getOccupationFor( unit, false );
        if ( occupation == null )
        {
            if ( ! traceOccupation )
            {
                LogBuilder lb = new LogBuilder( 64 );
                getOccupationFor( unit, false, lb );
                lb.log( logger, Level.WARNING );
            }
            return false;
        }
        return occupation.install( unit );
    }

    /**
     * Can this colony reduce its population voluntarily?
     *
     * This is generally the case, but can be prevented by buildings
     * such as the stockade in classic mode.
     *
     * @return True if the population can be reduced.
     */
    public boolean canReducePopulation( )
    {
        return getUnitCount( ) > applyModifiers( 0f, getGame( ).getTurn( ),
                                                 Modifier.MINIMUM_COLONY_SIZE );
    }

    /**
     * Gets the message to display if the colony can not reduce its
     * population.
     *
     * @return A <code>StringTemplate</code> describing why a colony
     *     can not reduce its population, or null if it can.
     */
    public StringTemplate getReducePopulationMessage( )
    {
        if ( canReducePopulation( ) ) return null;
        Set< Modifier > modifierSet = getModifiers( Modifier.MINIMUM_COLONY_SIZE );
        if ( modifierSet.isEmpty( ) ) return null;
        Modifier modifier = modifierSet.iterator( ).next( );
        FreeColObject source = modifier.getSource( );
        if ( source instanceof BuildingType )
        {
            // If the modifier source is a building type, use the
            // building in the colony, which may be of a different
            // level to the modifier source.
            // This prevents the stockade modifier from matching a
            // colony-fort, and thus the message attributing the
            // failure to reduce population to a non-existing
            // stockade, BR#3522055.
            source = getBuilding( ( BuildingType ) source ).getType( );
        }
        return StringTemplate.template( "model.colony.minimumColonySize" )
                .addName( "%object%", source );
    }

    /**
     * Gets the message to display if a colony can not build something.
     *
     * @param buildable The <code>BuildableType</code> that can not be built.
     * @return A <code>ModelMessage</code> describing the build failure.
     */
    public ModelMessage getUnbuildableMessage( BuildableType buildable )
    {
        return new ModelMessage( ModelMessage.MessageType.WARNING,
                                 "model.colony.unbuildable", this, buildable )
                .addName( "%colony%", getName( ) )
                .addNamed( "%object%", buildable );
    }

    /**
     * Returns 1, 0, or -1 to indicate that government would improve,
     * remain the same, or deteriorate if the colony had the given
     * population.
     *
     * @param unitCount The proposed population for the colony.
     * @return 1, 0 or -1.
     */
    public int governmentChange( int unitCount )
    {
        final Specification spec = getSpecification( );
        final int veryBadGovernment
                = spec.getInteger( GameOptions.VERY_BAD_GOVERNMENT_LIMIT );
        final int badGovernment
                = spec.getInteger( GameOptions.BAD_GOVERNMENT_LIMIT );
        final int veryGoodGovernment
                = spec.getInteger( GameOptions.VERY_GOOD_GOVERNMENT_LIMIT );
        final int goodGovernment
                = spec.getInteger( GameOptions.GOOD_GOVERNMENT_LIMIT );

        int rebelPercent = calculateSoLPercentage( unitCount, getLiberty( ) );
        int rebelCount = calculateRebels( unitCount, rebelPercent );
        int loyalistCount = unitCount - rebelCount;

        int result = 0;
        if ( rebelPercent >= veryGoodGovernment )
        { // There are no tories left.
            if ( sonsOfLiberty < veryGoodGovernment )
            {
                result = 1;
            }
        }
        else if ( rebelPercent >= goodGovernment )
        {
            if ( sonsOfLiberty >= veryGoodGovernment )
            {
                result = - 1;
            }
            else if ( sonsOfLiberty < goodGovernment )
            {
                result = 1;
            }
        }
        else
        {
            if ( sonsOfLiberty >= goodGovernment )
            {
                result = - 1;
            }
            else
            { // Now that no bonus is applied, penalties may.
                if ( loyalistCount > veryBadGovernment )
                {
                    if ( tories <= veryBadGovernment )
                    {
                        result = - 1;
                    }
                }
                else if ( loyalistCount > badGovernment )
                {
                    if ( tories <= badGovernment )
                    {
                        result = - 1;
                    }
                    else if ( tories > veryBadGovernment )
                    {
                        result = 1;
                    }
                }
                else
                {
                    if ( tories > badGovernment )
                    {
                        result = 1;
                    }
                }
            }
        }
        return result;
    }

    public ModelMessage checkForGovMgtChangeMessage( )
    {
        final Specification spec = getSpecification( );
        final int veryBadGovernment
                = spec.getInteger( GameOptions.VERY_BAD_GOVERNMENT_LIMIT );
        final int badGovernment
                = spec.getInteger( GameOptions.BAD_GOVERNMENT_LIMIT );
        final int veryGoodGovernment
                = spec.getInteger( GameOptions.VERY_GOOD_GOVERNMENT_LIMIT );
        final int goodGovernment
                = spec.getInteger( GameOptions.GOOD_GOVERNMENT_LIMIT );

        String msgId = null;
        int number = 0;
        ModelMessage.MessageType msgType = ModelMessage.MessageType.GOVERNMENT_EFFICIENCY;
        if ( sonsOfLiberty >= veryGoodGovernment )
        {
            // there are no tories left
            if ( oldSonsOfLiberty < veryGoodGovernment )
            {
                msgId = "model.colony.veryGoodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = veryGoodGovernment;
            }
        }
        else if ( sonsOfLiberty >= goodGovernment )
        {
            if ( oldSonsOfLiberty == veryGoodGovernment )
            {
                msgId = "model.colony.lostVeryGoodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = veryGoodGovernment;
            }
            else if ( oldSonsOfLiberty < goodGovernment )
            {
                msgId = "model.colony.goodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = goodGovernment;
            }
        }
        else
        {
            if ( oldSonsOfLiberty >= goodGovernment )
            {
                msgId = "model.colony.lostGoodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = goodGovernment;
            }

            // Now that no bonus is applied, penalties may.
            if ( tories > veryBadGovernment )
            {
                if ( oldTories <= veryBadGovernment )
                {
                    // government has become very bad
                    msgId = "model.colony.veryBadGovernment";
                }
            }
            else if ( tories > badGovernment )
            {
                if ( oldTories <= badGovernment )
                {
                    // government has become bad
                    msgId = "model.colony.badGovernment";
                }
                else if ( oldTories > veryBadGovernment )
                {
                    // government has improved, but is still bad
                    msgId = "model.colony.governmentImproved1";
                }
            }
            else if ( oldTories > badGovernment )
            {
                // government was bad, but has improved
                msgId = "model.colony.governmentImproved2";
            }
        }

        GoodsType bells = getSpecification( ).getGoodsType( "model.goods.bells" );
        return ( msgId == null ) ? null
                : new ModelMessage( msgType, msgId, this, bells )
                .addName( "%colony%", getName( ) )
                .addAmount( "%number%", number );
    }

    /**
     * Signal to the colony that its population is changing.
     * Called from Unit.setLocation when a unit moves into or out of this
     * colony, but *not* if it is moving within the colony.
     */
    public void updatePopulation( )
    {
        updateSoL( );
        updateProductionBonus( );
        if ( getOwner( ).isAI( ) )
        {
            firePropertyChange( Colony.REARRANGE_WORKERS, true, false );
        }
    }

    /**
     * Signal to the colony that a unit is moving in or out or
     * changing its internal work location to one with a different
     * teaching ability.  This requires either checking for a new
     * teacher or student, or clearing any existing education
     * relationships.
     *
     * @param unit The <code>Unit</code> that is changing its education state.
     * @param enable If true, check for new education opportunities, otherwise
     *     clear existing ones.
     */
    public void updateEducation( Unit unit, boolean enable )
    {
        WorkLocation wl = unit.getWorkLocation( );
        if ( wl == null )
        {
            throw new RuntimeException( "updateEducation(" + unit
                                                + ") unit not at work location." );
        }
        else if ( wl.getColony( ) != this )
        {
            throw new RuntimeException( "updateEducation(" + unit
                                                + ") unit not at work location in this colony." );
        }
        if ( enable )
        {
            if ( wl.canTeach( ) )
            {
                Unit student = unit.getStudent( );
                if ( student == null
                        && ( student = findStudent( unit ) ) != null )
                {
                    unit.setStudent( student );
                    student.setTeacher( unit );
                    unit.setTurnsOfTraining( 0 );// Teacher starts teaching
                    unit.changeWorkType( null );
                }
            }
            else
            {
                Unit teacher = unit.getTeacher( );
                if ( teacher == null
                        && ( teacher = findTeacher( unit ) ) != null )
                {
                    unit.setTeacher( teacher );
                    teacher.setStudent( unit );
                }
            }
        }
        else
        {
            if ( wl.canTeach( ) )
            {
                Unit student = unit.getStudent( );
                if ( student != null )
                {
                    student.setTeacher( null );
                    unit.setStudent( null );
                    unit.setTurnsOfTraining( 0 );// Teacher stops teaching
                }
            }
            else
            {
                Unit teacher = unit.getTeacher( );
                if ( teacher != null )
                {
                    teacher.setStudent( null );
                    unit.setTeacher( null );
                }
            }
        }
    }

    /**
     * Does this colony have undead units?
     *
     * @return True if this colony has undead units.
     */
    public boolean isUndead( )
    {
        Unit u = getFirstUnit( );
        return u != null && u.isUndead( );
    }

    /**
     * Gets the apparent number of units at this colony.
     * Used in client enemy colonies
     *
     * @return The apparent number of <code>Unit</code>s at this colony.
     */
    public int getDisplayUnitCount( )
    {
        return ( displayUnitCount > 0 ) ? displayUnitCount : getUnitCount( );
    }

    /**
     * Sets the apparent number of units at this colony.
     * Used in client enemy colonies
     *
     * @param count The new apparent number of <code>Unit</code>s at
     *     this colony.
     */
    public void setDisplayUnitCount( int count )
    {
        this.displayUnitCount = count;
    }


    // Defence, offense and trade response

    /**
     * Gets the best defender type available to this colony.
     *
     * @return The best available defender type.
     */
    public UnitType getBestDefenderType( )
    {
        UnitType bestDefender = null;
        for ( UnitType unitType : getSpecification( ).getUnitTypeList( ) )
        {
            if ( unitType.getDefence( ) > 0
                    && ( bestDefender == null
                    || bestDefender.getDefence( ) < unitType.getDefence( ) )
                    && ! unitType.hasAbility( Ability.NAVAL_UNIT )
                    && unitType.isAvailableTo( getOwner( ) ) )
            {
                bestDefender = unitType;
            }
        }
        return bestDefender;
    }

    /**
     * Gets the total defence power.
     *
     * @return The total defence power.
     */
    public double getTotalDefencePower( )
    {
        final CombatModel cm = getGame( ).getCombatModel( );
        return getTile( ).getUnitList( ).stream( )
                .filter( Unit::isDefensiveUnit )
                .mapToDouble( u -> cm.getDefencePower( null, u ) ).sum( );
    }

    /**
     * Determines whether this colony is sufficiently unprotected and
     * contains something worth pillaging.  To be called by CombatModels
     * when the attacker has defeated an unarmed colony defender.
     *
     * @param attacker The <code>Unit</code> that has defeated the defender.
     * @return True if the attacker can pillage this colony.
     */
    public boolean canBePillaged( Unit attacker )
    {
        return ! hasStockade( )
                && attacker.hasAbility( Ability.PILLAGE_UNPROTECTED_COLONY )
                && ! ( getBurnableBuildings( ).isEmpty( )
                && getTile( ).getNavalUnits( ).isEmpty( )
                && ( getLootableGoodsList( ).isEmpty( )
                || ! attacker.getType( ).canCarryGoods( )
                || ! attacker.hasSpaceLeft( ) )
                && ! canBePlundered( ) );
    }

    /**
     * Checks if this colony can be plundered.  That is, can it yield
     * non-zero gold.
     *
     * @return True if at least one piece of gold can be plundered from this
     *     colony.
     */
    public boolean canBePlundered( )
    {
        return owner.checkGold( 1 );
    }

    /**
     * Gets the buildings in this colony that could be burned by a raid.
     *
     * @return A list of burnable buildings.
     */
    public List< Building > getBurnableBuildings( )
    {
        return getBuildings( ).stream( )
                .filter( Building::canBeDamaged ).collect( Collectors.toList( ) );
    }

    /**
     * Gets a list of all stored goods in this colony, suitable for
     * being looted.
     *
     * @return A list of lootable goods in this colony.
     */
    public List< Goods > getLootableGoodsList( )
    {
        return getGoodsContainer( ).getGoods( ).stream( )
                .filter( g -> g.getType( ).isStorable( ) )
                .collect( Collectors.toList( ) );
    }

    /**
     * Returns <code>true</code> if the number of enemy combat units
     * on all tiles that belong to the colony exceeds the number of
     * friendly combat units. At the moment, only the colony owner's
     * own units are considered friendly, but that could be extended
     * to include the units of allied players.
     *
     * FIXME: if a colony is under siege, it should not be possible to
     * put units outside the colony, unless those units are armed.
     *
     * @return a <code>boolean</code> value
     */
    public boolean isUnderSiege( )
    {
        int friendlyUnits = 0;
        int enemyUnits = 0;
        for ( ColonyTile colonyTile : colonyTiles )
        {
            for ( Unit unit : colonyTile.getWorkTile( ).getUnitList( ) )
            {
                if ( unit.getOwner( ) == getOwner( ) )
                {
                    if ( unit.isDefensiveUnit( ) )
                    {
                        friendlyUnits++;
                    }
                }
                else if ( getOwner( ).atWarWith( unit.getOwner( ) ) )
                {
                    if ( unit.isOffensiveUnit( ) )
                    {
                        enemyUnits++;
                    }
                }
            }
        }
        return enemyUnits > friendlyUnits;
    }

    /**
     * Evaluate this colony for a given player.
     *
     * @param player The <code>Player</code> to evaluate for.
     * @return A value for the player.
     */
    public int evaluateFor( Player player )
    {
        if ( player.isAI( )
                && player.getNumberOfSettlements( ) < 5 )
        {// FIXME: magic#
            return Integer.MIN_VALUE;
        }
        int result;
        if ( player.owns( this ) )
        {
            result = getAvailableWorkLocations( ).stream( )
                    .mapToInt( wl -> wl.evaluateFor( player ) ).sum( )
                    + getTile( ).getUnitList( ).stream( )
                    .mapToInt( u -> u.evaluateFor( player ) ).sum( )
                    + getCompactGoods( ).stream( )
                    .mapToInt( g -> g.evaluateFor( player ) ).sum( );
        }
        else
        { // Much guesswork
            result = getDisplayUnitCount( ) * 1000
                    + 500 // Some useful goods?
                    + 200 * ( int ) getTile( ).getSurroundingTiles( 0, 1 ).stream( )
                    .filter( t -> t.getOwningSettlement( ) == this ).count( );
            Building stockade = getStockade( );
            if ( stockade != null ) result *= stockade.getLevel( );
        }
        return result;
    }


    // Education

    /**
     * Returns true if this colony has a schoolhouse and the unit type is a
     * skilled unit type with a skill level not exceeding the level of the
     * schoolhouse. @see Building#canAdd
     *
     * @param unit The unit to add as a teacher.
     * @return <code>true</code> if this unit type could be added.
     */
    public boolean canTrain( Unit unit )
    {
        return canTrain( unit.getType( ) );
    }

    /**
     * Returns true if this colony has a schoolhouse and the unit type is a
     * skilled unit type with a skill level not exceeding the level of the
     * schoolhouse. The number of units already in the schoolhouse and
     * the availability of pupils are not taken into account. @see
     * Building#canAdd
     *
     * @param unitType The unit type to add as a teacher.
     * @return <code>true</code> if this unit type could be added.
     */
    public boolean canTrain( UnitType unitType )
    {
        return hasAbility( Ability.TEACH )
                && any( buildingMap.values( ),
                        b -> b.canTeach( ) && b.canAddType( unitType ) );
    }

    /**
     * Gets a list of all teachers currently present in the school
     * building.
     *
     * @return A list of teacher <code>Unit</code>s.
     */
    public List< Unit > getTeachers( )
    {
        List< Unit > teachers = new ArrayList<>( );
        for ( Building building : buildingMap.values( ) )
        {
            if ( building.canTeach( ) )
            {
                teachers.addAll( building.getUnitList( ) );
            }
        }
        return teachers;
    }

    /**
     * Find a teacher for the specified student.
     * Do not search if ALLOW_STUDENT_SELECTION is true--- it is the
     * player's job then.
     *
     * @param student The student <code>Unit</code> that needs a teacher.
     * @return A potential teacher, or null of none found.
     */
    public Unit findTeacher( Unit student )
    {
        if ( getSpecification( ).getBoolean( GameOptions.ALLOW_STUDENT_SELECTION ) )
        {
            return null; // No automatic assignment
        }
        for ( Building building : getBuildings( ) )
        {
            if ( building.canTeach( ) )
            {
                for ( Unit unit : building.getUnitList( ) )
                {
                    if ( unit.getStudent( ) == null
                            && student.canBeStudent( unit ) ) { return unit; }
                }
            }
        }
        return null;
    }

    /**
     * Find a student for the specified teacher.
     * Do not search if ALLOW_STUDENT_SELECTION is true--- its the
     * player's job then.
     *
     * @param teacher The teacher <code>Unit</code> that needs a student.
     * @return A potential student, or null of none found.
     */
    public Unit findStudent( final Unit teacher )
    {
        if ( getSpecification( ).getBoolean( GameOptions.ALLOW_STUDENT_SELECTION ) )
        {
            return null; // No automatic assignment
        }
        Unit student = null;
        GoodsType expertProduction = teacher.getType( ).getExpertProduction( );
        int skillLevel = INFINITY;
        for ( Unit potentialStudent : getUnitList( ) )
        {
            /**
             * Always pick the student with the least skill first.
             * Break ties by favouring the one working in the teacher's trade,
             * otherwise first applicant wins.
             */
            if ( potentialStudent.getTeacher( ) == null
                    && potentialStudent.canBeStudent( teacher )
                    && ( student == null
                    || potentialStudent.getSkillLevel( ) < skillLevel
                    || ( potentialStudent.getSkillLevel( ) == skillLevel
                    && potentialStudent.getWorkType( ) == expertProduction ) ) )
            {
                student = potentialStudent;
                skillLevel = student.getSkillLevel( );
            }
        }
        return student;
    }


    // Production and consumption

    /**
     * Does this colony produce a goods type?
     *
     * This is more reliable than checking net or total production,
     * either of which might be cancelling to zero.
     *
     * @param goodsType The <code>GoodsType</code> to check.
     * @return True if goods type is produced.
     */
    public boolean isProducing( GoodsType goodsType )
    {
        return productionCache.isProducing( goodsType );
    }

    /**
     * Does this colony consume a goods type?
     *
     * This is more reliable than checking net or total consumption,
     * either of which might be cancelling to zero.
     *
     * @param goodsType The <code>GoodsType</code> to check.
     * @return True if goods type is consumed.
     */
    public boolean isConsuming( GoodsType goodsType )
    {
        return productionCache.isConsuming( goodsType );
    }

    /**
     * Get a list of all {@link Consumer}s in the colony sorted by
     * priority. Consumers include all object that consume goods,
     * e.g. Units, Buildings and BuildQueues.
     *
     * @return a list of consumers
     */
    public List< Consumer > getConsumers( )
    {
        List< Consumer > result = new ArrayList<>( );
        result.addAll( getUnitList( ) );
        result.addAll( buildingMap.values( ) );
        result.add( buildQueue );
        result.add( populationQueue );

        Collections.sort( result, Consumer.COMPARATOR );
        return result;
    }

    /**
     * Returns the number of goods of a given type used by the settlement
     * each turn.
     *
     * @param goodsType <code>GoodsType</code> values
     * @return an <code>int</code> value
     */
    @Override
    public int getConsumptionOf( GoodsType goodsType )
    {
        final Specification spec = getSpecification( );
        int result = super.getConsumptionOf( goodsType );
        if ( spec.getGoodsType( "model.goods.bells" ).equals( goodsType ) )
        {
            result -= spec.getInteger( "model.option.unitsThatUseNoBells" );
        }
        return Math.max( 0, result );
    }

    /**
     * Gets the combined production of all food types.
     *
     * @return an <code>int</code> value
     */
    public int getFoodProduction( )
    {
        return getSpecification( ).getFoodGoodsTypeList( ).stream( )
                .mapToInt( ft -> getTotalProductionOf( ft ) ).sum( );
    }

    /**
     * Get the number of turns before starvation occurs at this colony
     * with current production levels.
     *
     * @return The number of turns before starvation occurs, or negative
     *     if it will not.
     */
    public int getStarvationTurns( )
    {
        final GoodsType foodType = getSpecification( ).getPrimaryFoodType( );
        final int food = getGoodsCount( foodType );
        final int newFood = getAdjustedNetProductionOf( foodType );
        return ( newFood >= 0 ) ? - 1 : food / - newFood;
    }

    /**
     * Get the number of turns before a new colonist will be born in
     * this colony with current production levels.
     *
     * @return A number of turns, or negative if no colonist will be born.
     */
    public int getNewColonistTurns( )
    {
        final GoodsType foodType = getSpecification( ).getPrimaryFoodType( );
        final int food = getGoodsCount( foodType );
        final int newFood = getAdjustedNetProductionOf( foodType );
        return ( food + newFood >= Settlement.FOOD_PER_COLONIST ) ? 1
                : ( newFood <= 0 ) ? - 1
                : ( Settlement.FOOD_PER_COLONIST - food ) / newFood + 1;
    }


    /**
     * Get the current production <code>Modifier</code>, which is
     * generated from the current production bonus.
     *
     * @param goodsType The <code>GoodsType</code> to produce.
     * @return A list of suitable <code>Modifier</code>s.
     */
    public List< Modifier > getProductionModifiers( GoodsType goodsType )
    {
        if ( productionBonus == 0 ) return Collections.< Modifier >emptyList( );
        Modifier mod = new Modifier( goodsType.getId( ), productionBonus,
                                     Modifier.ModifierType.ADDITIVE,
                                     Specification.SOL_MODIFIER_SOURCE );
        mod.setModifierIndex( Modifier.COLONY_PRODUCTION_INDEX );
        List< Modifier > result = new ArrayList<>( );
        result.add( mod );
        return result;
    }

    /**
     * Get the net production of the given goods type.
     *
     * (Also part of interface TradeLocation)
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return an <code>int</code> value
     */
    public int getNetProductionOf( GoodsType goodsType )
    {
        return productionCache.getNetProductionOf( goodsType );
    }

    /**
     * Is a work location productive?
     *
     * @param workLocation The <code>WorkLocation</code> to check.
     * @return True if something is being produced at the
     *     <code>WorkLocation</code>.
     */
    public boolean isProductive( WorkLocation workLocation )
    {
        ProductionInfo info = productionCache.getProductionInfo( workLocation );
        return info != null && info.getProduction( ) != null
                && ! info.getProduction( ).isEmpty( )
                && info.getProduction( ).get( 0 ).getAmount( ) > 0;
    }

    /**
     * Returns the net production of the given GoodsType adjusted by
     * the possible consumption of BuildQueues.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return an <code>int</code> value
     */
    public int getAdjustedNetProductionOf( GoodsType goodsType )
    {
        int result = productionCache.getNetProductionOf( goodsType );
        for ( BuildQueue< ? > queue : new BuildQueue< ? >[]{ buildQueue,
                populationQueue } )
        {
            ProductionInfo info = productionCache.getProductionInfo( queue );
            if ( info != null )
            {
                result += AbstractGoods.getCount( goodsType,
                                                  info.getConsumption( ) );
            }
        }
        return result;
    }

    /**
     * Gets a copy of the current production map.
     * Useful in the server at the point net production is applied to a colony.
     *
     * @return A copy of the current production map.
     */
    protected TypeCountMap< GoodsType > getProductionMap( )
    {
        return productionCache.getProductionMap( );
    }

    /**
     * Returns the ProductionInfo for the given Object.
     *
     * @param object an <code>Object</code> value
     * @return a <code>ProductionInfo</code> value
     */
    public ProductionInfo getProductionInfo( Object object )
    {
        return productionCache.getProductionInfo( object );
    }

    /**
     * Invalidates the production cache.
     */
    public void invalidateCache( )
    {
        productionCache.invalidate( );
    }

    /**
     * Can this colony produce certain goods?
     *
     * @param goodsType The <code>GoodsType</code> to check production of.
     * @return True if the goods can be produced.
     */
    public boolean canProduce( GoodsType goodsType )
    {
        return ( getNetProductionOf( goodsType ) > 0 )
                ? true // Obviously:-)

                // Breeding requires the breedable number to be present
                : ( goodsType.isBreedable( ) )
                ? getGoodsCount( goodsType ) >= goodsType.getBreedingNumber( )

                // Is there a work location that can produce the goods, with
                // positive generic production potential and all inputs satisfied?
                : any( getWorkLocationsForProducing( goodsType ),
                       wl -> wl.getGenericPotential( goodsType ) > 0
                               && all( wl.getInputs( ), ag -> canProduce( ag.getType( ) ) ) );
    }


    // Planning support

    /** Container class for tile exploration or improvement suggestions. */
    public static class TileImprovementSuggestion
    {

        /**
         * Comparator to order suggestions by descending improvement
         * amount.
         */
        public static final Comparator< TileImprovementSuggestion > descendingAmountComparator
                = new Comparator< TileImprovementSuggestion >( )
        {
            @Override
            public int compare( TileImprovementSuggestion tis1,
                                TileImprovementSuggestion tis2 )
            {
                int cmp = tis2.amount - tis1.amount;
                if ( cmp == 0 ) cmp = tis2.tile.compareTo( tis1.tile );
                return cmp;
            }
        };

        /** The tile to explore or improve. */
        public Tile tile;
        /** The tile improvement to make, or if null to explore an LCR. */
        public TileImprovementType tileImprovementType;
        /** The expected improvement.  INFINITY for LCRs. */
        public int amount;

        public TileImprovementSuggestion( Tile tile, TileImprovementType t,
                                          int amount )
        {
            this.tile = tile;
            this.tileImprovementType = t;
            this.amount = amount;
        }

        public boolean isExploration( )
        {
            return this.tileImprovementType == null;
        }
    }

    ;

    /**
     * Collect suggestions for tiles that need exploration or
     * improvement (which may depend on current use within the colony).
     *
     * @return A list of <code>TileImprovementSuggestion</code>s.
     */
    public List< TileImprovementSuggestion > getTileImprovementSuggestions( )
    {
        final Specification spec = getSpecification( );
        List< TileImprovementSuggestion > result = new ArrayList<>( );

        // Encourage exploration of neighbouring rumours.
        for ( Tile tile : getTile( ).getSurroundingTiles( 1 ) )
        {
            if ( tile.hasLostCityRumour( ) )
            {
                result.add( new TileImprovementSuggestion( tile, null, INFINITY ) );
            }
        }

        // Consider improvements for all colony tiles
        for ( ColonyTile ct : getColonyTiles( ) )
        {
            final Tile tile = ct.getWorkTile( );
            if ( tile == null
                    || tile.getOwningSettlement( ) != this ) { continue; }

            for ( TileImprovementType t : spec.getTileImprovementTypeList( ) )
            {
                if ( t.isNatural( ) ) continue;
                int improvement = ct.improvedBy( t );
                if ( improvement > 0 )
                {
                    result.add( new TileImprovementSuggestion( tile, t,
                                                               improvement ) );
                }
            }
        }
        Collections.sort( result,
                          TileImprovementSuggestion.descendingAmountComparator );
        return result;
    }

    /**
     * Finds another unit in this colony that would be better at doing the
     * job of the specified unit.
     *
     * @param expert The <code>Unit</code> to consider.
     * @return A better expert, or null if none available.
     */
    public Unit getBetterExpert( Unit expert )
    {
        GoodsType production = expert.getWorkType( );
        UnitType expertType = expert.getType( );
        GoodsType expertise = expertType.getExpertProduction( );
        Unit bestExpert = null;
        int bestImprovement = 0;

        if ( production == null || expertise == null
                || production == expertise ) { return null; }

        // We have an expert not doing the job of their expertise.
        // Check if there is a non-expert doing the job instead.
        for ( Unit nonExpert : getUnitList( ) )
        {
            if ( nonExpert.getWorkType( ) != expertise
                    || nonExpert.getType( ) == expertType ) { continue; }

            // We have found a unit of a different type doing the
            // job of this expert's expertise now check if the
            // production would be better if the units swapped
            // positions.
            int expertProductionNow = 0;
            int nonExpertProductionNow = 0;
            int expertProductionPotential = 0;
            int nonExpertProductionPotential = 0;

            // Get the current and potential productions for the
            // work location of the expert.
            WorkLocation ewl = expert.getWorkLocation( );
            if ( ewl != null )
            {
                expertProductionNow = ewl.getPotentialProduction( expertise,
                                                                  expert.getType( ) );
                nonExpertProductionPotential
                        = ewl.getPotentialProduction( expertise,
                                                      nonExpert.getType( ) );
            }

            // Get the current and potential productions for the
            // work location of the non-expert.
            WorkLocation nwl = nonExpert.getWorkTile( );
            if ( nwl != null )
            {
                nonExpertProductionNow = nwl.getPotentialProduction( expertise,
                                                                     nonExpert.getType( ) );
                expertProductionPotential
                        = nwl.getPotentialProduction( expertise, expertType );
            }

            // Find the unit that achieves the best improvement.
            int improvement = expertProductionPotential
                    + nonExpertProductionPotential
                    - expertProductionNow
                    - nonExpertProductionNow;
            if ( improvement > bestImprovement )
            {
                bestImprovement = improvement;
                bestExpert = nonExpert;
            }
        }
        return bestExpert;
    }

    /**
     * Determine if there is a problem with the production of a given
     * goods type.
     *
     * @param goodsType The <code>GoodsType</code> to check.
     * @return A collection of warning messages.
     */
    public Collection< StringTemplate > getProductionWarnings( GoodsType goodsType )
    {
        List< StringTemplate > result = new ArrayList<>( );
        final int amount = getGoodsCount( goodsType );
        final int production = getNetProductionOf( goodsType );
        int waste;

        if ( goodsType.isStorable( ) )
        {
            if ( goodsType.limitIgnored( ) )
            {
                if ( goodsType.isFoodType( ) )
                {
                    int starve = getStarvationTurns( );
                    if ( starve == 0 )
                    {
                        result.add( StringTemplate
                                            .template( "model.colony.starving" )
                                            .addName( "%colony%", getName( ) ) );
                    }
                    else if ( starve <= Colony.FAMINE_TURNS )
                    {
                        result.add( StringTemplate
                                            .template( "model.colony.famineFeared" )
                                            .addName( "%colony%", getName( ) )
                                            .addAmount( "%number%", starve ) );
                    }
                }
            }
            else if ( ! getExportData( goodsType ).getExported( )
                    && ( waste = amount + production - getWarehouseCapacity( ) ) > 0 )
            {
                result.add( StringTemplate
                                    .template( "model.building.warehouseSoonFull" )
                                    .addNamed( "%goods%", goodsType )
                                    .addName( "%colony%", getName( ) )
                                    .addAmount( "%amount%", waste ) );
            }
        }

        BuildableType currentlyBuilding = getCurrentlyBuilding( );
        if ( currentlyBuilding != null )
        {
            for ( AbstractGoods goods : currentlyBuilding.getRequiredGoods( ) )
            {
                if ( goods.getType( ).equals( goodsType )
                        && amount < goods.getAmount( ) )
                {
                    int needsAmount = goods.getAmount( ) - amount;
                    result.add( StringTemplate
                                        .template( "model.colony.buildableNeedsGoods" )
                                        .addName( "%colony%", getName( ) )
                                        .addNamed( "%buildable%", currentlyBuilding )
                                        .addAmount( "%amount%", needsAmount )
                                        .addNamed( "%goodsType%", goodsType ) );
                }
            }
        }

        for ( WorkLocation wl : getWorkLocationsForProducing( goodsType ) )
        {
            ProductionInfo info = getProductionInfo( wl );
            if ( info == null ) continue;
            StringTemplate t = getInsufficientProductionMessage( info,
                                                                 AbstractGoods.findByType( goodsType,
                                                                                           info.getProductionDeficit( ) ) );
            if ( t != null ) result.add( t );
        }
        for ( WorkLocation wl : getWorkLocationsForConsuming( goodsType ) )
        {
            ProductionInfo info = getProductionInfo( wl );
            if ( info == null ) continue;
            List< AbstractGoods > deficit = info.getProductionDeficit( );
            if ( deficit.isEmpty( ) ) continue;
            for ( AbstractGoods ag : wl.getOutputs( ) )
            {
                if ( ag.getType( ).isStorable( ) ) continue;
                StringTemplate t = getInsufficientProductionMessage( info,
                                                                     AbstractGoods.findByType( ag.getType( ), deficit ) );
                if ( t != null ) result.add( t );
            }
        }

        return result;
    }

    /**
     * Get a message about insufficient production for a building
     *
     * @param info The <code>ProductionInfo</code> for the work location.
     * @param deficit The <code>AbstractGoods</code> in deficit.
     * @return A suitable <code>StringTemplate</code> or null if none required.
     */
    private StringTemplate getInsufficientProductionMessage( ProductionInfo info,
                                                             AbstractGoods deficit )
    {
        if ( info == null || deficit == null ) return null;

        List< AbstractGoods > input = info.getConsumptionDeficit( );
        if ( input.isEmpty( ) ) return null;
        StringTemplate label = StringTemplate.label( ", " );
        for ( AbstractGoods ag : input ) label.addStringTemplate( ag.getLabel( ) );

        return StringTemplate.template( "model.colony.insufficientProduction" )
                .addName( "%colony%", getName( ) )
                .addNamed( "%outputType%", deficit.getType( ) )
                .addAmount( "%outputAmount%", deficit.getAmount( ) )
                .addStringTemplate( "%consumptionDeficit%", label );
    }

    /**
     * Check if a goods type is still useful to this colony.
     *
     * In general, all goods are useful.  However post-independence there is
     * no need for more liberty once Sol% reaches 100, nor immigration.
     * Note the latter may change when we implement sailing to other European
     * ports.
     *
     * @param goodsType The <code>GoodsType</code> to check.
     */
    public boolean goodsUseful( GoodsType goodsType )
    {
        if ( getOwner( ).getPlayerType( ) == Player.PlayerType.INDEPENDENT )
        {
            if ( ( goodsType.isLibertyType( ) && getSoLPercentage( ) >= 100 )
                    || goodsType.isImmigrationType( ) ) { return false; }
        }
        return true;
    }

    /**
     * Special goods need modifiers applied when changed, and immigration
     * accumulates to the owner.
     *
     * @param goodsType The <code>GoodsType</code> to modify.
     * @param amount The amount of modification.
     */
    private void modifySpecialGoods( GoodsType goodsType, int amount )
    {
        final Turn turn = getGame( ).getTurn( );
        Set< Modifier > mods;

        mods = goodsType.getModifiers( Modifier.LIBERTY );
        if ( ! mods.isEmpty( ) )
        {
            int liberty = ( int ) applyModifiers( amount, turn, mods );
            modifyLiberty( liberty );
        }

        mods = goodsType.getModifiers( Modifier.IMMIGRATION );
        if ( ! mods.isEmpty( ) )
        {
            int migration = ( int ) applyModifiers( amount, turn, mods );
            modifyImmigration( migration );
            getOwner( ).modifyImmigration( migration );
        }
    }

    /**
     * Creates a temporary copy of this colony for planning purposes.
     *
     * A simple colony.copy() can not work because all the colony
     * tiles will be left referring to uncopied work tiles which the
     * colony-copy does not own, which prevents them being used as
     * valid work locations.  We have to copy the colony tile (which
     * includes the colony), and fix up all the colony tile work tiles
     * to point to copies of the original tile, and fix the ownership
     * of those tiles.
     *
     * @return A scratch version of this colony.
     */
    public Colony copyColony( )
    {
        final Game game = getGame( );
        Tile tile = getTile( );
        Tile tileCopy = tile.copy( game, tile.getClass( ) );
        Colony colony = tileCopy.getColony( );
        for ( ColonyTile ct : colony.getColonyTiles( ) )
        {
            Tile wt;
            if ( ct.isColonyCenterTile( ) )
            {
                wt = tileCopy;
            }
            else
            {
                wt = ct.getWorkTile( );
                wt = wt.copy( game, wt.getClass( ) );
                if ( wt.getOwningSettlement( ) == this )
                {
                    wt.setOwningSettlement( colony );
                }
            }
            ct.setWorkTile( wt );
        }
        return colony;
    }

    /**
     * Finds the corresponding FreeColObject from another copy of this colony.
     *
     * @param fco The <code>FreeColObject</code> in the other colony.
     * @return The corresponding <code>FreeColObject</code> in this
     *     colony, or null if not found.
     */
    @SuppressWarnings( "unchecked" )
    public < T extends FreeColObject > T getCorresponding( T fco )
    {
        final String id = fco.getId( );
        if ( fco instanceof WorkLocation )
        {
            for ( WorkLocation t : getAllWorkLocations( ) )
            {
                if ( t.getId( ).equals( id ) ) return ( T ) t;
            }
        }
        else if ( fco instanceof Tile )
        {
            if ( getTile( ).getId( ).equals( id ) ) return ( T ) getTile( );
            for ( ColonyTile ct : getColonyTiles( ) )
            {
                if ( ct.getWorkTile( ).getId( ).equals( id ) ) return ( T ) ct.getWorkTile( );
            }
        }
        else if ( fco instanceof Unit )
        {
            for ( Unit t : getUnitList( ) )
            {
                if ( t.getId( ).equals( id ) ) return ( T ) t;
            }
            for ( Unit t : getTile( ).getUnitList( ) )
            {
                if ( t.getId( ).equals( id ) ) return ( T ) t;
            }
        }
        return null;
    }


    // Override FreeColObject

    /**
     * {@inheritDoc}
     */
    @Override
    public Set< Ability > getAbilities( String id, FreeColGameObjectType type,
                                        Turn turn )
    {
        if ( turn == null ) turn = getGame( ).getTurn( );
        Set< Ability > result = super.getAbilities( id, type, turn );
        // Owner abilities also apply to colonies
        if ( owner != null ) result.addAll( owner.getAbilities( id, type, turn ) );
        return result;
    }


    // Override FreeColGameObject

    /**
     * {@inheritDoc}
     */
    @Override
    public List< FreeColGameObject > getDisposeList( )
    {
        List< FreeColGameObject > objects = new ArrayList<>( );
        for ( WorkLocation workLocation : getAllWorkLocations( ) )
        {
            objects.addAll( workLocation.getDisposeList( ) );
        }
        objects.addAll( super.getDisposeList( ) );
        return objects;
    }


    // Interface Location (from Settlement via GoodsLocation
    //   via UnitLocation)
    //   The unit list in UnitLocation is replaced in Colonies.
    // Inherits
    //   FreeColObject.getId
    //   Settlement.getTile
    //   Settlement.getLocationLabel
    //   GoodsLocation.canAdd
    //   GoodsLocation.getGoodsContainer
    //   Settlement.getSettlement

    /**
     * {@inheritDoc}
     */
    @Override
    public StringTemplate getLocationLabelFor( Player player )
    {
        // Everyone can always work out a colony name.
        return StringTemplate.name( getName( ) );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add( Locatable locatable )
    {
        if ( locatable instanceof Unit )
        {
            return joinColony( ( Unit ) locatable );
        }
        return super.add( locatable );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean remove( Locatable locatable )
    {
        if ( locatable instanceof Unit )
        {
            Location loc = locatable.getLocation( );
            if ( loc instanceof WorkLocation )
            {
                WorkLocation wl = ( WorkLocation ) loc;
                if ( wl.getColony( ) == this )
                {
                    return wl.remove( locatable );
                }
            }
            return false;
        }
        return super.remove( locatable );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean contains( Locatable locatable )
    {
        if ( locatable instanceof Unit )
        {
            return any( getAvailableWorkLocations( ),
                        wl -> wl.contains( locatable ) );
        }
        return super.contains( locatable );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getUnitCount( )
    {
        return getCurrentWorkLocations( ).stream( )
                .mapToInt( wl -> wl.getUnitCount( ) ).sum( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List< Unit > getUnitList( )
    {
        ArrayList< Unit > units = new ArrayList<>( );
        for ( WorkLocation wl : getCurrentWorkLocations( ) )
        {
            units.addAll( wl.getUnitList( ) );
        }
        return units;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Location up( )
    {
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toShortString( )
    {
        return getName( );
    }


    // Interface UnitLocation
    // Inherits
    //   UnitLocation.getSpaceTaken [Irrelevant!]
    //   UnitLocation.moveToFront [Irrelevant!]
    //   UnitLocation.clearUnitList [Irrelevant!]
    //   Settlement.equipForRole
    //   Settlement.getNoAddReason


    // Interface GoodsLocation

    /**
     * {@inheritDoc}
     */
    @Override
    public int getGoodsCapacity( )
    {
        return ( int ) applyModifiers( 0f, getGame( ).getTurn( ),
                                       Modifier.WAREHOUSE_STORAGE );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addGoods( GoodsType type, int amount )
    {
        super.addGoods( type, amount );
        productionCache.invalidate( type );
        modifySpecialGoods( type, amount );
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Goods removeGoods( GoodsType type, int amount )
    {
        Goods removed = super.removeGoods( type, amount );
        productionCache.invalidate( type );
        if ( removed != null ) modifySpecialGoods( type, - removed.getAmount( ) );
        return removed;
    }


    // Settlement

    /**
     * {@inheritDoc}
     */
    @Override
    public String getImageKey( )
    {
        String key;
        if ( isUndead( ) )
        {
            key = ".undead";
        }
        else
        {
            int count = getDisplayUnitCount( );
            key = ( count <= 3 ) ? ".small"
                    : ( count <= 7 ) ? ".medium"
                    : ".large";
            String stockade = getStockadeKey( );
            if ( stockade != null ) key += "." + stockade;
        }
        return "image.tileitem." + getType( ).getId( ) + key;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Unit getDefendingUnit( Unit attacker )
    {
        if ( displayUnitCount > 0 )
        {
            // There are units, but we don't see them
            return null;
        }

        // Note that this function will only return a unit working
        // inside the colony.  Typically, colonies are also defended
        // by units outside the colony on the same tile.  To consider
        // units outside the colony as well, use
        // @see Tile#getDefendingUnit instead.
        //
        // Returns an arbitrary unarmed land unit unless Paul Revere
        // is present as founding father, in which case the unit can
        // be armed as well.
        List< Unit > unitList = getUnitList( );

        Unit defender = null;
        double defencePower = - 1.0;
        for ( Unit nextUnit : unitList )
        {
            double unitPower = getGame( ).getCombatModel( )
                    .getDefencePower( attacker, nextUnit );
            if ( Unit.betterDefender( defender, defencePower,
                                      nextUnit, unitPower ) )
            {
                defender = nextUnit;
                defencePower = unitPower;
            }
        }
        if ( defender == null )
        {
            throw new IllegalStateException( "Colony " + getName( )
                                                     + " contains no units!" );
        }
        return defender;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double getDefenceRatio( )
    {
        return getTotalDefencePower( ) / ( 1 + getUnitCount( ) );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isBadlyDefended( )
    {
        return getTotalDefencePower( ) < 0.95 * getUnitCount( ) - 2.5;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public RandomRange getPlunderRange( Unit attacker )
    {
        if ( canBePlundered( ) )
        {
            int upper = ( owner.getGold( ) * ( getUnitCount( ) + 1 ) )
                    / ( owner.getColoniesPopulation( ) + 1 );
            if ( upper > 0 ) return new RandomRange( 100, 1, upper + 1, 1 );
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getSoL( )
    {
        return sonsOfLiberty;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getUpkeep( )
    {
        return buildingMap.values( ).stream( )
                .mapToInt( b -> b.getType( ).getUpkeep( ) ).sum( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getTotalProductionOf( GoodsType goodsType )
    {
        return getCurrentWorkLocations( ).stream( )
                .mapToInt( wl -> wl.getTotalProductionOf( goodsType ) ).sum( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canProvideGoods( List< AbstractGoods > requiredGoods )
    {
        // Unlike priceGoods, this takes goods "reserved" for other
        // purposes into account.
        BuildableType buildable = getCurrentlyBuilding( );
        for ( AbstractGoods goods : requiredGoods )
        {
            int available = getGoodsCount( goods.getType( ) );

            int breedingNumber = goods.getType( ).getBreedingNumber( );
            if ( breedingNumber != GoodsType.INFINITY )
            {
                available -= breedingNumber;
            }

            if ( buildable != null )
            {
                available -= AbstractGoods.getCount( goods.getType( ),
                                                     buildable.getRequiredGoods( ) );
            }

            if ( available < goods.getAmount( ) ) return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasContacted( Player player )
    {
        return player != null
                && ( player.isEuropean( )
                || getOwner( ).getStance( player ) != Stance.UNCONTACTED );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StringTemplate getAlarmLevelLabel( Player player )
    {
        Stance stance = getOwner( ).getStance( player );
        return StringTemplate.template( "model.colony." + stance.getKey( ) )
                .addStringTemplate( "%nation%", getOwner( ).getNationLabel( ) );
    }


    // Interface TradeLocation
    //   getGoodsCount provided in GoodsContainer

    /**
     * {@inheritDoc}
     */
    @Override
    public int getExportAmount( GoodsType goodsType, int turns )
    {
        final int present = Math.max( 0, getGoodsCount( goodsType )
                + turns * getNetProductionOf( goodsType ) );
        final ExportData ed = getExportData( goodsType );
        return Math.max( 0, present - ed.getExportLevel( ) );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getImportAmount( GoodsType goodsType, int turns )
    {
        if ( goodsType.limitIgnored( ) ) return GoodsContainer.HUGE_CARGO_SIZE;

        final int present = Math.max( 0, getGoodsCount( goodsType )
                + turns * getNetProductionOf( goodsType ) );
        int capacity = getWarehouseCapacity( );
        return Math.max( 0, capacity - present );
    }


    //
    // Miscellaneous low level
    //

    /**
     * Add port ability to non-landlocked colonies.
     */
    protected void addPortAbility( )
    {
        addAbility( new Ability( Ability.HAS_PORT ) );
    }

    /**
     * Check the integrity of the build queues.  Catches build fails
     * due to broken requirements.
     *
     * @param fix Fix problems if possible.
     * @return Negative if there are problems remaining, zero if
     *     problems were fixed, positive if no problems found at all.
     */
    public int checkBuildQueueIntegrity( boolean fix )
    {
        int result = 1;
        List< BuildableType > buildables = buildQueue.getValues( );
        List< BuildableType > assumeBuilt = new ArrayList<>( );
        for ( int i = 0; i < buildables.size( ); i++ )
        {
            BuildableType bt = buildables.get( i );
            NoBuildReason reason = getNoBuildReason( bt, assumeBuilt );
            if ( reason == NoBuildReason.NONE )
            {
                assumeBuilt.add( bt );
            }
            else if ( fix )
            {
                buildQueue.remove( i );
                result = Math.min( result, 0 );
            }
            else
            {
                result = - 1;
            }
        }
        List< UnitType > unitTypes = populationQueue.getValues( );
        assumeBuilt.clear( );
        for ( int i = 0; i < unitTypes.size( ); i++ )
        {
            UnitType ut = unitTypes.get( i );
            NoBuildReason reason = getNoBuildReason( ut, assumeBuilt );
            if ( reason == NoBuildReason.NONE )
            {
                assumeBuilt.add( ut );
            }
            else if ( fix )
            {
                populationQueue.remove( i );
                result = Math.min( result, 0 );
            }
            else
            {
                result = - 1;
            }
        }
        return result;
    }


    // Override FreeColGameObject

    /**
     * {@inheritDoc}
     */
    @Override
    public int checkIntegrity( boolean fix )
    {
        int result = super.checkIntegrity( fix );

        // @compat 0.10.x
        if ( ! isLandLocked( ) && ! hasAbility( Ability.HAS_PORT ) )
        {
            if ( fix )
            {
                addPortAbility( );
                result = Math.min( result, 0 );
            }
            else
            {
                result = - 1;
            }
        }
        // end @compat 0.10.x

        return Math.min( result, checkBuildQueueIntegrity( fix ) );
    }


    // Serialization

    private static final String BUILD_QUEUE_TAG = "buildQueueItem";
    private static final String ESTABLISHED_TAG = "established";
    private static final String IMMIGRATION_TAG = "immigration";
    private static final String LIBERTY_TAG = "liberty";
    private static final String PRODUCTION_BONUS_TAG = "productionBonus";
    private static final String NAME_TAG = "name";
    private static final String OLD_SONS_OF_LIBERTY_TAG = "oldSonsOfLiberty";
    private static final String OLD_TORIES_TAG = "oldTories";
    private static final String POPULATION_QUEUE_TAG = "populationQueueItem";
    private static final String SONS_OF_LIBERTY_TAG = "sonsOfLiberty";
    private static final String TORIES_TAG = "tories";
    private static final String UNIT_COUNT_TAG = "unitCount";


    /**
     * {@inheritDoc}
     */
    @Override
    protected void writeAttributes( FreeColXMLWriter xw ) throws XMLStreamException
    {
        super.writeAttributes( xw );

        // Delegated from Settlement
        xw.writeAttribute( NAME_TAG, getName( ) );

        xw.writeAttribute( ESTABLISHED_TAG, established.getNumber( ) );

        // SoL has to be visible for the popular support bonus to be
        // visible to an attacking rebel player.
        xw.writeAttribute( SONS_OF_LIBERTY_TAG, sonsOfLiberty );

        if ( xw.validFor( getOwner( ) ) )
        {

            xw.writeAttribute( OLD_SONS_OF_LIBERTY_TAG, oldSonsOfLiberty );

            xw.writeAttribute( TORIES_TAG, tories );

            xw.writeAttribute( OLD_TORIES_TAG, oldTories );

            xw.writeAttribute( LIBERTY_TAG, liberty );

            xw.writeAttribute( IMMIGRATION_TAG, immigration );

            xw.writeAttribute( PRODUCTION_BONUS_TAG, productionBonus );

        }
        else
        {

            int uc = getDisplayUnitCount( );
            if ( uc <= 0 )
            {
                logger.warning( "Unit count fail: " + uc + " id=" + getId( )
                                        + " unitCount=" + getUnitCount( )
                                        + " scope=" + xw.getWriteScope( )
                                        + " player=" + xw.getWriteScope( ).getClient( ) + "\n"
                                        + net.sf.freecol.common.debug.FreeColDebugger.stackTraceToString( ) );
            }
            xw.writeAttribute( UNIT_COUNT_TAG, uc );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void writeChildren( FreeColXMLWriter xw ) throws XMLStreamException
    {
        super.writeChildren( xw );

        if ( xw.validFor( getOwner( ) ) )
        {

            for ( Entry< String, ExportData > e : mapEntriesByKey( exportData ) )
            {
                e.getValue( ).toXML( xw );
            }

            for ( WorkLocation workLocation : getSortedCopy( getAllWorkLocations( ) ) )
            {
                workLocation.toXML( xw );
            }

            for ( BuildableType item : buildQueue.getValues( ) )
            { // In order!
                xw.writeStartElement( BUILD_QUEUE_TAG );

                xw.writeAttribute( ID_ATTRIBUTE_TAG, item );

                xw.writeEndElement( );
            }

            for ( BuildableType item : populationQueue.getValues( ) )
            { // In order
                xw.writeStartElement( POPULATION_QUEUE_TAG );

                xw.writeAttribute( ID_ATTRIBUTE_TAG, item );

                xw.writeEndElement( );
            }

        }
        else
        {
            // Special case.  Serialize stockade-class buildings to
            // otherwise unprivileged clients as the stockade level is
            // visible to anyone who can see the colony.  This should
            // have no other information leaks because stockade
            // buildings have no production or units inside.
            Building stockade = getStockade( );
            if ( stockade != null ) stockade.toXML( xw );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void readAttributes( FreeColXMLReader xr ) throws XMLStreamException
    {
        super.readAttributes( xr );

        established = new Turn( xr.getAttribute( ESTABLISHED_TAG, 0 ) );

        sonsOfLiberty = xr.getAttribute( SONS_OF_LIBERTY_TAG, 0 );

        oldSonsOfLiberty = xr.getAttribute( OLD_SONS_OF_LIBERTY_TAG, 0 );

        tories = xr.getAttribute( TORIES_TAG, 0 );

        oldTories = xr.getAttribute( OLD_TORIES_TAG, 0 );

        liberty = xr.getAttribute( LIBERTY_TAG, 0 );

        immigration = xr.getAttribute( IMMIGRATION_TAG, 0 );

        productionBonus = xr.getAttribute( PRODUCTION_BONUS_TAG, 0 );

        displayUnitCount = xr.getAttribute( UNIT_COUNT_TAG, - 1 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void readChildren( FreeColXMLReader xr ) throws XMLStreamException
    {
        // Clear containers.
        colonyTiles.clear( );
        buildingMap.clear( );
        exportData.clear( );
        buildQueue.clear( );
        populationQueue.clear( );

        super.readChildren( xr );

        invalidateCache( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void readChild( FreeColXMLReader xr ) throws XMLStreamException
    {
        final Specification spec = getSpecification( );
        final Game game = getGame( );
        final String tag = xr.getLocalName( );

        if ( BUILD_QUEUE_TAG.equals( tag ) )
        {
            BuildableType bt = xr.getType( spec, ID_ATTRIBUTE_TAG,
                                           BuildableType.class, ( BuildableType ) null );
            if ( bt != null ) buildQueue.add( bt );
            xr.closeTag( BUILD_QUEUE_TAG );

        }
        else if ( POPULATION_QUEUE_TAG.equals( xr.getLocalName( ) ) )
        {
            UnitType ut = xr.getType( spec, ID_ATTRIBUTE_TAG,
                                      UnitType.class, ( UnitType ) null );
            if ( ut != null ) populationQueue.add( ut );
            xr.closeTag( POPULATION_QUEUE_TAG );

        }
        else if ( Building.getXMLElementTagName( ).equals( tag ) )
        {
            addBuilding( xr.readFreeColGameObject( game, Building.class ) );

        }
        else if ( ColonyTile.getXMLElementTagName( ).equals( tag ) )
        {
            colonyTiles.add( xr.readFreeColGameObject( game, ColonyTile.class ) );

        }
        else if ( ExportData.getXMLElementTagName( ).equals( tag ) )
        {
            ExportData data = new ExportData( xr );
            exportData.put( data.getId( ), data );

        }
        else
        {
            super.readChild( xr );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString( )
    {
        return getName( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getXMLTagName( ) { return getXMLElementTagName( ); }

    /**
     * Gets the tag name of the root element representing this object.
     *
     * @return "colony".
     */
    public static String getXMLElementTagName( )
    {
        return "colony";
    }
}
